package store.lunangangster.engine.impl.interceptor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.ProcessEngineConfiguration;
import store.lunangangster.engine.impl.agenda.NodeFlowEngineAgenda;
import store.lunangangster.engine.impl.cmd.Command;
import store.lunangangster.engine.impl.db.DbSqlSession;
import store.lunangangster.engine.impl.entity.ExecutionEntity;

public class CommandContext {

  private static final Logger log = LoggerFactory.getLogger(CommandContext.class);

  protected ProcessEngineConfiguration processEngineConfiguration;
  protected List<SessionListener> sessionListeners;
  protected Map<String, ExecutionEntity> involvedExecutions;  // 当前CommandContext涉及到的正在执行的Execution
  protected DbSqlSession dbSqlSession;
  protected Command<?> command;
  protected NodeFlowEngineAgenda agenda;
  protected Throwable exception;

  protected Object result;

  protected boolean isClose;

  public CommandContext(ProcessEngineConfiguration processEngineConfiguration, Command<?> command) {
    this.processEngineConfiguration = processEngineConfiguration;
    this.sessionListeners = new ArrayList<>();
    this.involvedExecutions = new HashMap<>();
    this.dbSqlSession = processEngineConfiguration.getDbSqlSessionFactory().openSession();
    this.command = command;
    this.agenda = processEngineConfiguration.getAgendaFactory().createAgenda(this);
  }

  public ProcessEngineConfiguration getProcessEngineConfiguration() {
    return processEngineConfiguration;
  }

  public void addSessionListener(SessionListener sessionListener) {
    sessionListeners.add(sessionListener);
  }

  public void addInvolvedExecution(ExecutionEntity execution) {
    if (execution != null) {
      involvedExecutions.put(execution.getId(), execution);
    }
  }

  public boolean hasInvolvedExecution() {
    return !involvedExecutions.isEmpty();
  }

  public DbSqlSession getDbSqlSession() {
    return dbSqlSession;
  }

  public Command<?> getCommand() {
    return command;
  }

  public NodeFlowEngineAgenda getAgenda() {
    return agenda;
  }

  public Throwable getException() {
    return exception;
  }

  public void setException(Throwable exception) {
    this.exception = exception;
  }

  public Object getResult() {
    return result;
  }

  public void setResult(Object result) {
    this.result = result;
  }

  public void close() {
    try {
      doBeforeFlush();
      if (exception == null) {
        dbSqlSession.flush(); // exception
      }
    } catch (Throwable e) {
      exception(e);
    } finally {
      if (exception == null) {
        doAfterFlush();
      }
    }

    try {
      doBeforeClose();
      dbSqlSession.close();  // exception
    } catch (Throwable e) {
      exception(e);
    } finally {
      if (exception == null) {
        doAfterClose();
      }
    }

    rethrowException();

    // 所有的CommandContext都是单线程的，不会出现线程安全问题
    isClose = true;
  }

  public boolean isClose() {
    return isClose;
  }

  public void exception(Throwable throwable) {
    if (exception == null) {
      exception = throwable;
    } else {
      log.error("发生在CommandContext中的子异常，根异常会在稍后抛出", throwable);
    }
  }

  private void doBeforeFlush() {
    try {
      for (SessionListener sessionListener : sessionListeners) {
        sessionListener.beforeFlush();
      }
    } catch (Throwable e) {
      exception(e);
    }
  }

  private void doAfterFlush() {
    try {
      for (SessionListener sessionListener : sessionListeners) {
        sessionListener.afterFlush();
      }
    } catch (Throwable e) {
      exception(e);
    }
  }

  private void doBeforeClose() {
    try {
      for (SessionListener sessionListener : sessionListeners) {
        sessionListener.beforeClose();
      }
    } catch (Throwable e) {
      exception(e);
    }
  }

  private void doAfterClose() {
    try {
      for (SessionListener sessionListener : sessionListeners) {
        sessionListener.afterClose();
      }
    } catch (Throwable e) {
      exception(e);
    }
  }

  private void rethrowException() {
    if (exception != null) {
      if (exception instanceof NodeFlowException) {
        throw (NodeFlowException) exception;
      } else {
        throw new NodeFlowException("发生在CommandContext中的根异常: ", exception);
      }
    }
  }

}
